#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h> //struct sockaddr_in
#include <arpa/inet.h>  //inet_ntoa()  inet_addr()
#include <unistd.h>     //close()
#include <cstring>
using namespace std;
/*
组播也是基于UDP的，局域网，广域网都可以
只要向组播ip发送，绑定组播ip的设备就可以收到组播消息
组播ip的网段范围是：224.0.0.0 ~ 239.255.255.255
只要发送，组播下的所有主机都会收到(端口号一致的)


流程：
1、socket：创建用于通信的套接字文件描述符
2、setsocketopt：设置套接字加入多播组   在IPPROTO_IP网络层  IP_ADD_MEMBERSHIP属性
        属性值为结构体类型
        struct ip_mreqn {
            struct in_addr imr_multiaddr; // 组播地址的网络字节序
            struct in_addr imr_address; // 接收端的主机地址
            int imr_ifindex;            // 网卡设备编号  一般为2  可以通过指令 ip ad 查看
            };
3、bind：绑定地址信息结构体
        IP：组播地址
        PORT：与发送端保持一致
4、recv、recvfrom、read：接收数据
5、close：关闭套接字
*/
int main(int argc, const char *argv[])
{
    // 1、创建用于通信的套接字文件描述符
    int recvfd = socket(AF_INET, SOCK_DGRAM, 0);
    // SOCK_DGRAM表示基于udp通信方式
    if (recvfd == -1)
    {
        perror("socket error");
        return -1;
    }
    printf("socket success sfd = %d\n", recvfd); // 3

    // 将该套接字加入多播组
    struct ip_mreqn imr;                                   // 多播组属性结构体
    imr.imr_multiaddr.s_addr = inet_addr("224.1.2.3");     // 组播地址
    imr.imr_address.s_addr = inet_addr("192.168.174.128"); // 本机ip地址
    imr.imr_ifindex = 2;                                   // 网卡编号
    // 调用设置网络属性函数
    if (setsockopt(recvfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr, sizeof(imr)) == -1)
    {
        perror("setsockopt error");
        return -1;
    }
    printf("成功加入多播组\n");

    // 2、绑定ip地址和端口号
    // 2.1 填充要绑定的ip地址和端口号结构体
    struct sockaddr_in rin;
    rin.sin_family = AF_INET;
    // 通信域
    rin.sin_port = htons(8888); // 端口号
    rin.sin_addr.s_addr = inet_addr("224.1.2.3");
    // 组播ip地址
    // 2.2 绑定工作
    // 参数1：要被绑定的套接字文件描述符
    // 参数2：要绑定的地址信息结构体，需要进行强制类型转换，防止警告
    // 参数3：参数2的大小
    if (bind(recvfd, (struct sockaddr *)&rin, sizeof(rin)) == -1)
    {
        perror("bind error");
        return -1;
    }
    printf("bind success\n");
    // 3、数据收发
    char rbuf[128] = "";
    while (1)
    {
        // 清空容器
        bzero(rbuf, sizeof(rbuf));
        // 从客户端中读取消息
        if (recvfrom(recvfd, rbuf, sizeof(rbuf), 0, NULL, NULL) == -1)
        {
            perror("recvfrom error");
            return -1;
        }
        printf("[读取的消息为]:%s\n", rbuf);
    }
    // 4、关闭套接字
    close(recvfd);
    std::cout << "Hello, World!" << std::endl;
    return 0;
}